//    Copyright (C) Kherty.  All rights reserved.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.Reflection;

namespace OpenLS.Spreadsheet.Internals
{
    public abstract class Group: INotifyPropertyChanged
    {
       
        internal static Group GetCommonAncestor (Group group1, Group group2)
        {
            if (group1.Depth > group2.Depth)
                return GetCommonAncestor(group1.Parent, group2);
            if (group2.Depth > group1.Depth)
                return GetCommonAncestor(group1, group2.Parent);
            if (group1 == group2)
                return group1;
            return GetCommonAncestor(group1.Parent, group2.Parent);

        }
        readonly ObservableCollection <ChildGroup>_children = new ObservableCollection<ChildGroup>();
        public ObservableCollection<ChildGroup> Children { get { return _children; } }
        internal IEnumerable<ChildGroup> GetChildren{get{
            foreach (ChildGroup child in Children)
            {
                yield return child;
            }}}
        public IEnumerable<ChildGroup> GetGroups (Predicate<ChildGroup> predicate)
        {
            foreach (ChildGroup group in Children)
            {
                if (predicate(group))
                    yield return group;
            }

        }

        protected void NotifyPropertyChanged(string propertyName)
        {
            Contract.Requires(GetType().GetProperty(propertyName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) != null);
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
        internal abstract Group Parent { get; }
        public void Expand(int depth)
        {
            if (depth == 0 && this is ChildGroup)
            {
                ((ChildGroup) this).IsExpanded = false;
                return;
            }
            if (this is ChildGroup)
                ((ChildGroup) this).IsExpanded = true;
            if (depth > 0)
            {
                foreach (var group in Children)
                {
                    group.Expand(depth - 1);
                }
            }
        }


        internal abstract bool Contains(Dimension dimension);
        internal Group GetContainer (Dimension dimension)
        {
            Group result = this;
            foreach (var child in Children)
            {
                if (child.Contains(dimension))
                    return child.GetContainer(dimension);
            }
            return result;
        }
        public abstract int Depth { get; }

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

        internal void AddChild(ChildGroup group)
        {
            Worksheet worksheet = Worksheet;
            if (worksheet.Workbook.UndoEnabled)
                worksheet.Workbook.AddUndoUnit(new GroupAddedUndoUnit(this, group));
            ChildGroup after = null;
            foreach (ChildGroup g in Children)
            {
                if (g.First.Index >= group.Last.Index)
                {
                    after = g;
                    break;
                }
            }
            int at = Children.Count;
            if (after != null)
                at = Children.IndexOf(after);
            Children.Insert(at, group);
            Root.NotifyPropertyChanged("MaxDepth");
        }

        internal abstract Group Root { get; }

        public int MaxDepth
        {
            get
            {
                int result = Depth;
                foreach (Group g in Children)
                {
                    result = Math.Max(result, g.MaxDepth);
                }
                return result;
            }
        }

        internal void RemoveChild(ChildGroup childGroup)
        {
            Worksheet worksheet = Worksheet;
            if (worksheet.Workbook.UndoEnabled)
                worksheet.Workbook.AddUndoUnit(new GroupRemovedUndoUnit(this, childGroup));

            Children.Remove(childGroup);
            Root.NotifyPropertyChanged("MaxDepth");
        }

        internal abstract Worksheet Worksheet { get; }

        protected Group TopParent
        {
            get
            {
                Contract.Ensures(Contract.Result<Group>() != null);
                if (Parent == null)
                    return this;
                return Parent.TopParent;
            }
        }

        internal abstract void Ungroup(Dimension first, Dimension next);
        protected abstract void UngroupNoChild(Dimension first, Dimension next);

        internal void GroupDimensions<T>(Dimension firstDimension, Dimension nextDimension) where T: Dimension
        {
            Group firstContainer = GetContainer(firstDimension);
            Group nextContainer = GetContainer(nextDimension);

            Dimension boundFirst = firstDimension;
            Dimension boundNext = nextDimension;
            foreach (ChildGroup group in firstContainer.GetAncestorsStrict(this))
            {
                group.ExpandBounds(ref boundFirst, ref boundNext);
            }
            foreach (ChildGroup group in nextContainer.GetAncestorsStrict(this))
            {
                group.ExpandBounds(ref boundFirst, ref boundNext);
            }
            var oldGroups = new List<ChildGroup>(GetGroups(
                                                                  group =>
                                                                  group.First.Index >= boundFirst.Index &&
                                                                  group.Last.Index <= boundNext.Index));
            foreach (ChildGroup group in oldGroups)
                RemoveChild(group);
            ChildGroup newGroup = CreateGroup(this, boundFirst, boundNext);
            newGroup.IsExpanded = true;
            foreach (ChildGroup oldGroup in oldGroups)
                oldGroup.TransferTo(newGroup);
            AddChild(newGroup);
        }

        public IEnumerable<ChildGroup> GetAncestorsStrict(Group ancestor)
        {
            if (this == ancestor)
                yield break;
            var g = this as ChildGroup;
            while (g != null)
            {
#if DEBUG
                Contract.Assert(this.IsDescendantOf(ancestor));
                Contract.Assert(g.IsDescendantOf(ancestor));
#endif
                yield return g;
                if (g.Parent == ancestor)
                    break;
                g = g.Parent as ChildGroup;
            }

        }

        public abstract bool IsDescendantOf(Group ancestor);
        
        public abstract ChildGroup CreateGroup(Group parent, Dimension first, Dimension next);


        internal abstract bool Intersects(Dimension first, Dimension next);

        internal void OnChildExpandedChanged()
        {
            foreach (var group in GetDescendants())
                group.NotifyPropertyChanged("FirstOffset");
        }

        private IEnumerable<ChildGroup> GetDescendants()
        {
            foreach (var child in _children)
            {
                yield return child;
                foreach (var d in child.GetDescendants())
                    yield return d;
            }

        }
    }
}